/**
 * LinkedLST, Kellen Donohue
 * May 5, 2011
 * SchemaRbParser.java - Parses a schema.rb file and returns java objects
 */

package edu.washington.cs.labspecimentransport.data;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;

/**
 * @author Kellen
 * 
 */

@SuppressWarnings("rawtypes")
public class SchemaRbParser {
	/**
	 * The file we are parsing
	 */
	Scanner reader;

	static Class booleanType;
	static Class integerType;
	static Class stringType;
	static Class datetimeType;

	/**
	 * Listing of fields as <String, Class> objects
	 */
	Map<String, Class> fields;

	/**
	 * Create a new object to parse the given file
	 * 
	 * @param file
	 *            Path to the file to parse
	 * @throws FileNotFoundException
	 *             If the given file can't be found
	 * @throws ClassNotFoundException
	 *             If one of the predefined classes can't be created.
	 */
	public SchemaRbParser(String file) throws FileNotFoundException,
			ClassNotFoundException {
		this.reader = new Scanner(new File(file));

		booleanType = Class.forName("java.lang.Boolean");
		integerType = Class.forName("java.lang.Integer");
		stringType = Class.forName("java.lang.String");
		datetimeType = Class.forName("java.util.Date");

		this.fields = new HashMap<String, Class>();
	}

	/**
	 * Now go ahead and parse the file
	 * 
	 * @return A list of rails models contained within the file
	 * @throws Exception
	 *             If a model in the file can't be de-pluralized
	 */
	public List<RailsModel> parseFile() throws Exception {
		List<RailsModel> result = new ArrayList<RailsModel>();

		while (this.reader.hasNext()) {
			String line = this.reader.nextLine();
			// Ignore blank lines and comments
			if (line.length() == 0 || line.startsWith("#")
					|| line.endsWith("  ")) {
				continue;
			}

			// Ignore method definition
			if (line.startsWith("ActiveRecord") || line.equals("end")) {
				continue;
			}

			// Parse the next model
			RailsModel rm = this.parseModel(line);
			result.add(rm);
		}
		return result;
	}

	/**
	 * Parse a model definition, given a model name
	 * 
	 * @throws Exception
	 *             If a model name can't be de-pluralized
	 */
	private RailsModel parseModel(String line) throws Exception {
		Scanner modelNameScanner = new Scanner(line);
		// Discard the first word, which is create_table
		modelNameScanner.next();
		// Get the next work, remove quotes and comma
		String name = modelNameScanner.next();
		name = name.replace("\"", "");
		name = name.replace(",", "");
		name = depluralize(name);
		RailsModel rm = new RailsModel(name);

		line = this.reader.nextLine();
		System.out.println("parsing " + name);
		while (!line.endsWith("end")) {
			Scanner lineScanner = new Scanner(line);

			// Pull out the type and cut off the first 3 characters
			String type = lineScanner.next().substring(2);
			Class typeClass = determineTypeFromString(type);

			// Grab the field name, remove quotes and comma
			String fieldName = lineScanner.next();
			fieldName = fieldName.replace("\"", "");
			fieldName = fieldName.replace(",", "");

			rm.fields.put(fieldName, typeClass);
			line = this.reader.nextLine();
		}

		// Every object has an id
		rm.fields.put("id", integerType);

		return rm;
	}

	/**
	 * Undo rails pluralization of class names
	 * 
	 * @param name
	 *            A plural name
	 * @return The singular form
	 * @throws Exception
	 *             If the pluralization can't be handled
	 */
	private String depluralize(String name) throws Exception {
		if (name.endsWith("s")) {
			return name.substring(0, name.length() - 1);
		}
		throw new Exception("Unknown pluarlization");
	}

	/**
	 * Given the name of a ruby type determine the corresponding Java type
	 * 
	 * @param type Name of the type - in ruby format
	 * @return  java class representing the type
	 */
	public static Class determineTypeFromString(String type) {
		if (type.equals("string")) {
			return stringType;
		} else if (type.equals("integer")) {
			return integerType;
		} else if (type.equals("boolean")) {
			return booleanType;
		} else if (type.equals("date") || type.equals("datetime")
				|| type.equals("time")) {
			return datetimeType;
		} else {
			throw new java.lang.IllegalArgumentException("Unsupported ruby type: "
					+ type);
		}
	}
}
